import Message from "@/mode/Message";
import Receipt from "@/mode/Receipt";
import SendCode from "@/utils/SendCode";
import ChatUtils from "@/utils/ChatUtils";
import vimConfig from "@/config/VimConfig";
import Auth from "@/api/Auth";
import ChatType from "@/utils/ChatType";
import { nextTick } from "vue";
import { useUserStore } from "@/store/userStore";
import { useChatStore } from "@/store/chatStore";

const ready = `{"code":${SendCode.READY}}`;
const ping = `{"code":${SendCode.PING}}`;

class WsRequest {
  lockReconnect: boolean;
  url: string | undefined;
  //是否主动关闭
  closeByUser: boolean;
  timeout: number;
  timeoutError: number;
  heartTask: NodeJS.Timeout | null;
  reconnectTimeoutTask: NodeJS.Timeout | null;
  socket: WebSocket | null;

  private static instance: WsRequest;

  private constructor() {
    this.lockReconnect = false; //避免重复连接
    this.url = "";
    //是否主动关闭
    this.closeByUser = false;
    //心跳检测 多少秒执行检测
    this.timeout = 3000;
    //超过多少秒没反应就重连
    this.timeoutError = 5000;
    this.heartTask = null;
    this.reconnectTimeoutTask = null;
    this.socket = null;
  }

  static getInstance() {
    if (!this.instance) {
      this.instance = new WsRequest();
    }
    return this.instance;
  }

  public init(): void {
    this.closeByUser = false;
    this.url = `${vimConfig.wsProtocol}://${Auth.getIp()}:${
      vimConfig.wsPort
    }?token=${Auth.getToken()}&client=${vimConfig.client}`;
    this.socket = new WebSocket(this.url);
    this.socket.onopen = () => {
      //告知服务器准备就绪
      this.send(ready);
      // 清除重连定时器
      if (this.reconnectTimeoutTask) {
        clearTimeout(this.reconnectTimeoutTask);
      }
      // 开启检测
      this.reset();
    };

    // 如果希望websocket连接一直保持，在close或者error上绑定重新连接方法。
    this.socket.onclose = () => {
      if (!this.closeByUser) {
        this.reconnect();
      }
    };

    this.socket.onerror = () => {
      this.reconnect();
    };

    this.socket.onmessage = (event: MessageEvent) => {
      const data = event.data;
      //防止每次心跳都要进行 JSON.parse 优化性能
      if (data === ping) {
        this.reset();
        return;
      }
      const sendInfo = JSON.parse(data);
      if (sendInfo.code === SendCode.MESSAGE) {
        this.onmessage(sendInfo.message);
      }
      //接受任何消息都说明当前连接是正常的
      this.reset();
    };
  }

  /**
   * 发送状态
   * @param value
   */
  send(value: string): void {
    this.socket?.send(value);
  }

  /**
   * 收到消息
   * @param message 消息
   */
  onmessage = (message: Message): void => {
    const user = useUserStore().getUser();
    //群聊里面，自己发的消息不再显示
    if (user?.id === message.fromId) {
      message.mine = true;
    }
    //友聊换chatId,chatId 不一样
    if (ChatType.FRIEND === message.type && user?.id !== message.fromId) {
      message.chatId = message.fromId;
    }
    useChatStore().pushMessage(message);
    //确保消息已经渲染到页面上了，再去滚动到底部
    nextTick(() => {
      ChatUtils.imageLoad("message-box");
    });
  };

  /**
   * 发送真正的聊天消息
   * @param message 消息
   */
  sendMessage(message: Message): void {
    const sendInfo = {
      code: SendCode.MESSAGE,
      message: message,
    };
    this.send(JSON.stringify(sendInfo));
  }

  /**
   *  立即验证连接有效性
   *  重置心跳检测和重连检测
   *  立刻发送一个心跳信息
   *  如果没有收到消息，就会执行重连
   */
  checkStatus(): void {
    // 清除定时器重新发送一个心跳信息
    if (this.heartTask) {
      clearTimeout(this.heartTask);
    }
    if (this.reconnectTimeoutTask) {
      clearTimeout(this.reconnectTimeoutTask);
    }
    this.lockReconnect = false;
    this.send(ping);
    //onmessage拿到消息就会清理 reconnectTimeoutTask，如果没有清理，就会执行重连
    this.reconnectTimeoutTask = setTimeout(() => {
      this.reconnect();
    }, this.timeoutError - this.timeout);
  }

  /**
   * 发送已读取消息
   * @param receipt 消息读取回执
   */
  sendRead(receipt: Receipt): void {
    const sendInfo = {
      code: SendCode.READ,
      message: receipt,
    };
    this.send(JSON.stringify(sendInfo));
  }

  /**
   *  reset和start方法主要用来控制心跳的定时。
   */
  reset(): void {
    // 清除定时器重新发送一个心跳信息
    if (this.heartTask) {
      clearTimeout(this.heartTask);
    }
    if (this.reconnectTimeoutTask) {
      clearTimeout(this.reconnectTimeoutTask);
    }
    this.lockReconnect = false;
    this.heartTask = setTimeout(() => {
      //这里发送一个心跳，后端收到后，返回一个心跳消息，
      //onmessage拿到返回的心跳就说明连接正常
      this.send(ping);
    }, this.timeout);
    //onmessage拿到消息就会清理 reconnectTimeoutTask，如果没有清理，就会执行重连
    this.reconnectTimeoutTask = setTimeout(() => {
      this.reconnect();
    }, this.timeoutError);
  }

  // 重连
  reconnect(): void {
    // 防止多个方法调用，多处重连
    if (this.lockReconnect) {
      return;
    }
    this.lockReconnect = true;
    //没连接上会一直重连，设置延迟避免请求过多
    this.reconnectTimeoutTask = setTimeout(() => {
      // 重新连接
      this.init();
      this.lockReconnect = false;
    }, this.timeoutError);
  }

  // 手动关闭
  close(): void {
    this.lockReconnect = false;
    //主动关闭
    if (this.heartTask) {
      clearTimeout(this.heartTask);
    }
    if (this.reconnectTimeoutTask) {
      clearTimeout(this.reconnectTimeoutTask);
    }
    this.closeByUser = true;
    if (this.socket) {
      this.socket.close();
    }
  }
}

export default WsRequest;
